import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

device = (
    "cuda"
    if torch.cuda.is_available()
    else "mps"
    if torch.backends.mps.is_available()
    else "cpu"
)
print(f"Using {device} device")


# 我们通过继承 nn.Module 来定义我们的神经网络，并在 __init__ 中初始化神经网络层。
# 每个 nn.Module 子类都在 forward 方法中实现对输入数据的操作。

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork().to(device)
print(model)

# 为了使用模型，我们将输入数据传递给它。
# 这将执行模型的 forward 方法，以及一些 后台操作。
# 不要直接调用 model.forward()！

# 在输入上调用模型会返回一个二维张量，其中 dim=0 对应于每个类的 10 个原始预测值的每个输出，dim=1 对应于每个输出的各个值。
# 我们通过将其传递给 nn.Softmax 模块的实例来获得预测概率。

X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")


# 让我们分解 FashionMNIST 模型中的层。
# 为了说明这一点，我们将获取一个大小为 28x28 的 3 张图像的样本 minibatch，并查看当我们将其传递给网络时会发生什么。

input_image = torch.rand(3,28,28)
print(input_image.size())

# 我们初始化 nn.Flatten 层，将每个 2D 28x28 图像转换为 784 个像素值的连续数组（minibatch 维度（在 dim=0 处）保持不变）

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())

# nn.layer()

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
# nn.ReLU()
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")

# nn.Sequential 是一个有序的模块容器。数据按定义的相同顺序传递给所有模块。
# 你可以使用顺序容器来组合一个像 seq_modules 这样的快速网络。

seq_modules = nn.Sequential(
    flatten,
    layer1,
    nn.ReLU(),
    nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)

# 神经网络中的许多层都是_参数化的_，即具有在训练期间优化的相关权重和偏差。
# 继承 nn.Module 会自动跟踪模型对象中定义的所有字段，并可以使用模型的 parameters() 或 named_parameters() 方法访问所有参数。

softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)

print(f"Model structure: {model}\n\n")

for name, param in model.named_parameters():
    print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")


